home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / Libraries / Apache 1.0 / src / mod_negotiation.c < prev    next >
Text File  |  1995-12-04  |  33KB  |  1,140 lines

  1.  
  2. /* ====================================================================
  3.  * Copyright (c) 1995 The Apache Group.  All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  * 1. Redistributions of source code must retain the above copyright
  10.  *    notice, this list of conditions and the following disclaimer. 
  11.  *
  12.  * 2. Redistributions in binary form must reproduce the above copyright
  13.  *    notice, this list of conditions and the following disclaimer in
  14.  *    the documentation and/or other materials provided with the
  15.  *    distribution.
  16.  *
  17.  * 3. All advertising materials mentioning features or use of this
  18.  *    software must display the following acknowledgment:
  19.  *    "This product includes software developed by the Apache Group
  20.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  21.  *
  22.  * 4. The names "Apache Server" and "Apache Group" must not be used to
  23.  *    endorse or promote products derived from this software without
  24.  *    prior written permission.
  25.  *
  26.  * 5. Redistributions of any form whatsoever must retain the following
  27.  *    acknowledgment:
  28.  *    "This product includes software developed by the Apache Group
  29.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  30.  *
  31.  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
  32.  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  33.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  34.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
  35.  * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  39.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  42.  * OF THE POSSIBILITY OF SUCH DAMAGE.
  43.  * ====================================================================
  44.  *
  45.  * This software consists of voluntary contributions made by many
  46.  * individuals on behalf of the Apache Group and was originally based
  47.  * on public domain software written at the National Center for
  48.  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
  49.  * For more information on the Apache Group and the Apache HTTP server
  50.  * project, please see <http://www.apache.org/>.
  51.  *
  52.  */
  53.  
  54.  
  55. /*
  56.  * mod_negotiation.c: keeps track of MIME types the client is willing to
  57.  * accept, and contains code to handle type arbitration.
  58.  *
  59.  * rst
  60.  */
  61.  
  62. #include "httpd.h"
  63. #include "http_config.h"
  64. #include "http_request.h"
  65. #include "http_core.h"
  66. #include "http_log.h"
  67.  
  68. /* Commands --- configuring document caching on a per (virtual?)
  69.  * server basis...
  70.  */
  71.  
  72. typedef struct {
  73.     array_header *language_priority;
  74. } neg_dir_config;
  75.  
  76. module negotiation_module;
  77.  
  78. void *create_neg_dir_config (pool *p, char *dummy)
  79. {
  80.     neg_dir_config *new =
  81.       (neg_dir_config *) palloc (p, sizeof (neg_dir_config));
  82.  
  83.     new->language_priority = make_array (p, 4, sizeof (char *));
  84.     return new;
  85. }
  86.  
  87. void *merge_neg_dir_configs (pool *p, void *basev, void *addv)
  88. {
  89.     neg_dir_config *base = (neg_dir_config *)basev;
  90.     neg_dir_config *add = (neg_dir_config *)addv;
  91.     neg_dir_config *new =
  92.       (neg_dir_config *) palloc (p, sizeof (neg_dir_config));
  93.  
  94.     /* give priority to the config in the subdirectory */
  95.     new->language_priority = append_arrays (p, add->language_priority,
  96.                         base->language_priority);
  97.     return new;
  98. }
  99.  
  100. char *set_language_priority (cmd_parms *cmd, void *n, char *lang)
  101. {
  102.     array_header *arr = ((neg_dir_config *) n)->language_priority;
  103.     char **langp = (char **) push_array (arr);
  104.  
  105.     *langp = pstrdup (arr->pool, lang);
  106.     return NULL;
  107. }
  108.  
  109. char *cache_negotiated_docs (cmd_parms *cmd, void *dummy, char *dummy2)
  110. {
  111.     void *server_conf = cmd->server->module_config;
  112.     
  113.     set_module_config (server_conf, &negotiation_module, "Cache");
  114.     return NULL;
  115. }
  116.  
  117. int do_cache_negotiated_docs (server_rec *s)
  118. {
  119.     return (get_module_config (s->module_config, &negotiation_module) != NULL);
  120. }
  121.  
  122. command_rec negotiation_cmds[] = {
  123. { "CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF, RAW_ARGS,
  124.     NULL },
  125. { "LanguagePriority", set_language_priority, NULL, OR_FILEINFO, ITERATE,
  126.     NULL },
  127. { NULL }
  128. };
  129.  
  130. /*
  131.  * TO DO --- error code 406.  Unfortunately, the specification for
  132.  *           a 406 reply in the current draft standard is unworkable;
  133.  *           we return 404 for these pending a workable spec. 
  134.  */
  135.  
  136. /* Record of available info on a media type specified by the client
  137.  * (we also use 'em for encodings and languages)
  138.  */
  139.  
  140. typedef struct accept_rec {
  141.     char *type_name;
  142.     float quality;
  143.     float max_bytes;
  144.     float level;
  145. } accept_rec;
  146.  
  147. /* Record of available info on a particular variant
  148.  *
  149.  * Note that a few of these fields are updated by the actual negotiation
  150.  * code.  These are:
  151.  *
  152.  * quality --- initialized to the value of qs, and subsequently jiggered
  153.  *             to reflect the client's preferences.  In particular, it
  154.  *             gets zeroed out if the variant has an unacceptable content
  155.  *             encoding, or if it is in a language which the client
  156.  *             doesn't accept and some other variant *is* in a language
  157.  *             the client accepts.
  158.  *
  159.  * level_matched --- initialized to zero.  Set to the value of level
  160.  *             if the client actually accepts this media type at that
  161.  *             level (and *not* if it got in on a wildcard).  See level_cmp
  162.  *             below.
  163.  */
  164.  
  165. typedef struct var_rec {
  166.     request_rec *sub_req;    /* May be NULL (is, for map files) */
  167.     char *type_name;
  168.     char *file_name;
  169.     char *content_encoding;
  170.     char *content_language;
  171.     float level;        /* Auxiliary to content-type... */
  172.     float qs;
  173.     float bytes;
  174.     int lang_index;
  175.     int is_pseudo_html;        /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
  176.  
  177.     /* Above are all written-once properties of the variant.  The
  178.      * two fields below are changed during negotiation:
  179.      */
  180.     
  181.     float quality;    
  182.     float level_matched;
  183. } var_rec;
  184.  
  185. /* Something to carry around the state of negotiation (and to keep
  186.  * all of this thread-safe)...
  187.  */
  188.  
  189. typedef struct {
  190.     pool *pool;
  191.     request_rec *r;
  192.     char *dir_name;
  193.     
  194.     array_header *accepts;    /* accept_recs */
  195.     array_header *accept_encodings;    /* accept_recs */
  196.     array_header *accept_langs;    /* accept_recs */
  197.     array_header *avail_vars;    /* available variants */
  198. } negotiation_state;
  199.  
  200. /* A few functions to manipulate var_recs.
  201.  * Cleaning out the fields...
  202.  */
  203.  
  204. void clean_var_rec (var_rec *mime_info)
  205. {
  206.     mime_info->sub_req = NULL;
  207.     mime_info->type_name = "";
  208.     mime_info->file_name = "";
  209.     mime_info->content_encoding = "";
  210.     mime_info->content_language = "";
  211.  
  212.     mime_info->is_pseudo_html = 0.0;
  213.     mime_info->level = 0.0;
  214.     mime_info->level_matched = 0.0;
  215.     mime_info->qs = 0.0;
  216.     mime_info->quality = 0.0;
  217.     mime_info->bytes = 0;
  218.     mime_info->lang_index = -1;
  219. }
  220.  
  221. /* Initializing the relevant fields of a variant record from the
  222.  * accept_info read out of its content-type, one way or another.
  223.  */
  224.  
  225. void set_mime_fields (var_rec *var, accept_rec *mime_info)
  226. {
  227.     var->type_name = mime_info->type_name;
  228.     var->qs = mime_info->quality;
  229.     var->quality = mime_info->quality; /* Initial quality is just qs */
  230.     var->level = mime_info->level;
  231.  
  232.     var->is_pseudo_html = 
  233.     (!strcmp (var->type_name, "text/html")
  234.      || !strcmp (var->type_name, INCLUDES_MAGIC_TYPE)
  235.      || !strcmp (var->type_name, INCLUDES_MAGIC_TYPE3));
  236. }
  237.  
  238. /*****************************************************************
  239.  *
  240.  * Parsing (lists of) media types and their parameters, as seen in
  241.  * HTTPD header lines and elsewhere.
  242.  */
  243.  
  244. /* Retrieve a token, spacing over it and returning a pointer to
  245.  * the first non-white byte afterwards.  Note that these tokens
  246.  * are delimited by semis and commas; and can also be delimited
  247.  * by whitespace at the caller's option.
  248.  */
  249.  
  250. char *get_token (pool *p, char **accept_line, int accept_white)
  251. {
  252.     char *ptr = *accept_line;
  253.     char *tok_start;
  254.     char *token;
  255.     int tok_len;
  256.   
  257.     /* Find first non-white byte */
  258.     
  259.     while (*ptr && isspace(*ptr))
  260.       ++ptr;
  261.  
  262.     tok_start = ptr;
  263.     
  264.     /* find token end, skipping over quoted strings.
  265.      * (comments are already gone).
  266.      */
  267.     
  268.     while (*ptr && (accept_white || !isspace(*ptr))
  269.        && *ptr != ';' && *ptr != ',')
  270.     {
  271.     if (*ptr++ == '"')
  272.         while (*ptr)
  273.             if (*ptr++ == '"') break;
  274.     }
  275.       
  276.     tok_len = ptr - tok_start;
  277.     token = palloc (p, tok_len + 1);
  278.     strncpy (token, tok_start, tok_len);
  279.     token[tok_len] = '\0';
  280.     
  281.     /* Advance accept_line pointer to the next non-white byte */
  282.  
  283.     while (*ptr && isspace(*ptr))
  284.       ++ptr;
  285.  
  286.     *accept_line = ptr;
  287.     return token;
  288. }
  289.  
  290. /*
  291.  * Get a single mime type entry --- one media type and parameters;
  292.  * enter the values we recognize into the argument accept_rec
  293.  */
  294.  
  295. char *get_entry (pool *p, accept_rec *result, char *accept_line)
  296. {
  297.     result->quality = 1.0;
  298.     result->max_bytes = 0.0;
  299.     result->level = 0.0;
  300.     
  301.     /* Note that this handles what I gather is the "old format",
  302.      *
  303.      *    Accept: text/html text/plain moo/zot
  304.      *
  305.      * without any compatibility kludges --- if the token after the
  306.      * MIME type begins with a semicolon, we know we're looking at parms,
  307.      * otherwise, we know we aren't.  (So why all the pissing and moaning
  308.      * in the CERN server code?  I must be missing something).
  309.      */
  310.     
  311.     result->type_name = get_token (p, &accept_line, 0);
  312.     str_tolower (result->type_name); /* You want case-insensitive,
  313.                       * you'll *get* case-insensitive.
  314.                       */
  315.     
  316.  
  317.     /* KLUDGE!!! Default HTML to level 2.0 unless the browser
  318.      * *explicitly* says something else.
  319.      */
  320.     
  321.     if (!strcmp (result->type_name, "text/html")
  322.     && result->level == 0.0)
  323.     result->level = 2.0;
  324.     else if (!strcmp (result->type_name, INCLUDES_MAGIC_TYPE))
  325.     result->level = 2.0;
  326.     else if (!strcmp (result->type_name, INCLUDES_MAGIC_TYPE3))
  327.     result->level = 3.0;
  328.  
  329.     while (*accept_line == ';') {
  330.     /* Parameters ... */
  331.  
  332.     char *parm;
  333.     char *cp;
  334.         
  335.     ++accept_line;
  336.     parm = get_token (p, &accept_line, 1);
  337.  
  338.     /* Look for 'var = value' --- and make sure the var is in lcase. */
  339.     
  340.     for (cp = parm; *cp && !isspace(*cp) && *cp != '='; ++cp)
  341.         *cp = tolower(*cp);
  342.  
  343.     if (!*cp) continue;    /* No '='; just ignore it. */
  344.         
  345.     *cp++ = '\0';        /* Delimit var */
  346.     while (*cp && (isspace(*cp) || *cp == '='))
  347.         ++cp;
  348.  
  349.     if (*cp == '"') ++cp;
  350.     
  351.     if (parm[0] == 'q'
  352.         && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
  353.         result->quality = atof(cp);
  354.     else if (parm[0] == 'm' && parm[1] == 'x' &&
  355.          parm[2] == 'b' && parm[3] == '\0')
  356.         result->max_bytes = atof(cp);
  357.     else if (parm[0] == 'l' && !strcmp (&parm[1], "evel"))
  358.         result->level = atof(cp);
  359.     }
  360.  
  361.     if (*accept_line == ',') ++accept_line;
  362.  
  363.     return accept_line;
  364. }
  365.          
  366.  
  367. /*****************************************************************
  368.  *
  369.  * Dealing with header lines ...
  370.  */
  371.  
  372. array_header *do_header_line (pool *p, char *accept_line)
  373. {
  374.     array_header *accept_recs = make_array (p, 40, sizeof (accept_rec));
  375.   
  376.     if (!accept_line) return accept_recs;
  377.     
  378.     while (*accept_line) {
  379.         accept_rec *new = (accept_rec *)push_array (accept_recs);
  380.     accept_line = get_entry (p, new, accept_line);
  381.     }
  382.  
  383.     return accept_recs;
  384. }
  385.  
  386. /*****************************************************************
  387.  *
  388.  * Handling header lines from clients...
  389.  */
  390.  
  391. negotiation_state *parse_accept_headers (request_rec *r)
  392. {
  393.     negotiation_state *new =
  394.         (negotiation_state *)palloc (r->pool, sizeof (negotiation_state));
  395.     table *hdrs = r->headers_in;
  396.  
  397.     new->pool = r->pool;
  398.     new->r = r;
  399.     new->dir_name = make_dirstr(r->pool, r->filename, count_dirs(r->filename));
  400.     
  401.     new->accepts = do_header_line (r->pool, table_get (hdrs, "Accept"));
  402.     new->accept_encodings =
  403.       do_header_line (r->pool, table_get (hdrs, "Accept-encoding"));
  404.     new->accept_langs =
  405.       do_header_line (r->pool, table_get (hdrs, "Accept-language"));
  406.     new->avail_vars = make_array (r->pool, 40, sizeof (var_rec));
  407.  
  408.     return new;
  409. }
  410.  
  411. /* Sometimes clients will give us no Accept info at all; this routine sets
  412.  * up the standard default for that case, and also arranges for us to be
  413.  * willing to run a CGI script if we find one.  (In fact, we set up to
  414.  * dramatically prefer CGI scripts in cases where that's appropriate,
  415.  * e.g., POST).
  416.  */
  417.  
  418. void maybe_add_default_encodings(negotiation_state *neg, int prefer_scripts)
  419. {
  420.     accept_rec *new_accept = (accept_rec *)push_array (neg->accepts); 
  421.   
  422.     new_accept->type_name = CGI_MAGIC_TYPE;
  423.     new_accept->quality = prefer_scripts ? 1e-20 : 1e20;
  424.     new_accept->level = 0.0;
  425.     new_accept->max_bytes = 0.0;
  426.  
  427.     if (neg->accepts->nelts > 1) return;
  428.     
  429.     new_accept = (accept_rec *)push_array (neg->accepts); 
  430.     
  431.     new_accept->type_name = "*/*";
  432.     new_accept->quality = 1.0;
  433.     new_accept->level = 0.0;
  434.     new_accept->max_bytes = 0.0;
  435. }
  436.  
  437. /*****************************************************************
  438.  *
  439.  * Parsing type-map files, in Roy's meta/http format augmented with
  440.  * #-comments.
  441.  */
  442.  
  443. /* Reading RFC822-style header lines, ignoring #-comments and
  444.  * handling continuations.
  445.  */
  446.  
  447. enum header_state { header_eof, header_seen, header_sep };
  448.  
  449. enum header_state get_header_line (char *buffer, int len, FILE *map)
  450. {
  451.     char *buf_end = buffer + len;
  452.     char *cp;
  453.     int c;
  454.     
  455.     /* Get a noncommented line */
  456.     
  457.     do {
  458.     if (fgets(buffer, MAX_STRING_LEN, map) == NULL)
  459.         return header_eof;
  460.     } while (buffer[0] == '#');
  461.     
  462.     /* If blank, just return it --- this ends information on this variant */
  463.     
  464.     for (cp = buffer; *cp && isspace (*cp); ++cp)
  465.       continue;
  466.  
  467.     if (*cp == '\0') return header_sep;
  468.  
  469.     /* If non-blank, go looking for header lines, but note that we still
  470.      * have to treat comments specially...
  471.      */
  472.  
  473.     cp += strlen(cp);
  474.     
  475.     while ((c = getc(map)) != EOF)
  476.     {
  477.     if (c == '#') {
  478.         /* Comment line */
  479.         while ((c = getc(map)) != EOF && c != '\n')
  480.            continue;
  481.     } else if (isspace(c)) {
  482.         /* Leading whitespace.  POSSIBLE continuation line
  483.          * Also, possibly blank --- if so, we ungetc() the final newline
  484.          * so that we will pick up the blank line the next time 'round.
  485.          */
  486.         
  487.         while (c != EOF && c != '\n' && isspace(c))
  488.             c = getc(map);
  489.  
  490.         ungetc (c, map);
  491.         
  492.         if (c == '\n') return header_seen; /* Blank line */
  493.  
  494.         /* Continuation */
  495.  
  496.         while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n')
  497.             *cp++ = c;
  498.  
  499.         *cp++ = '\n';
  500.         *cp = '\0';
  501.     } else {
  502.  
  503.         /* Line beginning with something other than whitespace */
  504.         
  505.         ungetc (c, map);
  506.         return header_seen;
  507.     }
  508.     }
  509.  
  510.     return header_seen;
  511. }
  512.  
  513. /* Stripping out RFC822 comments */
  514.  
  515. void strip_paren_comments (char *hdr)
  516. {
  517.     /* Hmmm... is this correct?  In Roy's latest draft, (comments) can nest! */
  518.   
  519.     while (*hdr) {
  520.     if (*hdr == '"') {
  521.         while (*++hdr && *hdr != '"')
  522.         continue;
  523.         ++hdr;
  524.     }
  525.     else if (*hdr == '(') {
  526.         while (*hdr && *hdr != ')')    *hdr++ = ' ';
  527.         
  528.         if (*hdr) *hdr++ = ' ';
  529.     }
  530.     else ++hdr;
  531.     }
  532. }
  533.  
  534. /* Getting to a header body from the header */
  535.  
  536. char *lcase_header_name_return_body (char *header, request_rec *r)
  537. {
  538.     char *cp = header;
  539.     
  540.     while (*cp && *cp != ':')
  541.         *cp++ = tolower(*cp);
  542.     
  543.     if (!*cp) {
  544.     log_reason ("Syntax error in type map --- no ':'", r->filename, r);
  545.     return NULL;
  546.     }
  547.  
  548.     do ++cp; while (*cp && isspace (*cp));
  549.  
  550.     if (!*cp) {
  551.     log_reason ("Syntax error in type map --- no header body",
  552.             r->filename, r);
  553.     return NULL;
  554.     }
  555.  
  556.     return cp;
  557. }
  558.  
  559. int read_type_map (negotiation_state *neg, char *map_name)
  560. {
  561.     request_rec *r = neg->r;
  562.     FILE *map = pfopen (neg->pool, map_name, "r");
  563.  
  564.     char buffer[MAX_STRING_LEN];
  565.     enum header_state hstate;
  566.     struct var_rec mime_info;
  567.     
  568.     if (map == NULL) {
  569.         log_reason("cannot access type map file", map_name, r);
  570.     return FORBIDDEN;
  571.     }
  572.  
  573.     clean_var_rec (&mime_info);
  574.     
  575.     do {
  576.     hstate = get_header_line (buffer, MAX_STRING_LEN, map);
  577.     
  578.     if (hstate == header_seen) {
  579.         char *body = lcase_header_name_return_body (buffer, neg->r);
  580.         
  581.         if (body == NULL) return SERVER_ERROR;
  582.         
  583.         strip_paren_comments (body);
  584.         
  585.         if (!strncmp (buffer, "uri:", 4)) {
  586.             mime_info.file_name = get_token (neg->pool, &body, 0);
  587.         }
  588.         else if (!strncmp (buffer, "content-type:", 13)) {
  589.         struct accept_rec accept_info;
  590.         
  591.         get_entry (neg->pool, &accept_info, body);
  592.         set_mime_fields (&mime_info, &accept_info);
  593.         }
  594.         else if (!strncmp (buffer, "content-length:", 15)) {
  595.         mime_info.bytes = atoi(body);
  596.         }
  597.         else if (!strncmp (buffer, "content-language:", 17)) {
  598.         mime_info.content_language = get_token (neg->pool, &body, 0);
  599.         str_tolower (mime_info.content_language);
  600.         }
  601.         else if (!strncmp (buffer, "content-encoding:", 17)) {
  602.         mime_info.content_encoding = get_token (neg->pool, &body, 0);
  603.         str_tolower (mime_info.content_encoding);
  604.         }
  605.     } else {
  606.         if (mime_info.quality > 0) {
  607.             void *new_var = push_array (neg->avail_vars);
  608.         memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
  609.         }
  610.         
  611.         clean_var_rec(&mime_info);
  612.     }
  613.     } while (hstate != header_eof);
  614.     
  615.     pfclose (neg->pool, map);
  616.     return OK;
  617. }
  618.  
  619. /*****************************************************************
  620.  *
  621.  * Same, except we use a filtered directory listing as the map...
  622.  */
  623.  
  624. int read_types_multi (negotiation_state *neg)
  625. {
  626.     request_rec *r = neg->r;
  627.     
  628.     char *filp;
  629.     int prefix_len;
  630.     DIR *dirp;
  631.     struct DIR_TYPE *dir_entry;
  632.     struct var_rec mime_info;
  633.     struct accept_rec accept_info;
  634.     void *new_var;
  635.  
  636.     clean_var_rec (&mime_info);
  637.  
  638.     if (!(filp = strrchr (r->filename, '/'))) return DECLINED; /* Weird... */
  639.  
  640.     ++filp;
  641.     prefix_len = strlen (filp);
  642.  
  643.     dirp = opendir (neg->dir_name); /* Not pool protected; sigh... */
  644.  
  645.     if (dirp == NULL) {
  646.         log_reason("cannot read directory for multi", neg->dir_name, r);
  647.     return FORBIDDEN;
  648.     }
  649.  
  650.     while ((dir_entry = readdir (dirp))) {
  651.     
  652.         request_rec *sub_req;
  653.       
  654.     /* Do we have a match? */
  655.     
  656.     if (strncmp (dir_entry->d_name, filp, prefix_len)) continue;
  657.     if (dir_entry->d_name[prefix_len] != '.') continue;
  658.     
  659.     /* Yep.  See if it's something which we have access to, and 
  660.      * which has a known type and encoding (as opposed to something
  661.      * which we'll be slapping default_type on later).
  662.      */
  663.     
  664.     sub_req = sub_req_lookup_file (dir_entry->d_name, r);
  665.     
  666.     if (sub_req->status != 200 || !sub_req->content_type) continue;
  667.     
  668.     /* If it's a map file, we use that instead of the map
  669.      * we're building...
  670.      */
  671.  
  672.     if (!strcmp (sub_req->content_type, MAP_FILE_MAGIC_TYPE)) {
  673.         closedir(dirp);
  674.         
  675.         neg->avail_vars->nelts = 0;
  676.         return read_type_map (neg, sub_req->filename);
  677.     }
  678.     
  679.     /* Have reasonable variant --- gather notes.
  680.      */
  681.     
  682.     mime_info.sub_req = sub_req;
  683.     mime_info.file_name = dir_entry->d_name;
  684.     mime_info.content_encoding = sub_req->content_encoding;
  685.     mime_info.content_language = sub_req->content_language;
  686.     
  687.     get_entry (neg->pool, &accept_info, sub_req->content_type);
  688.     set_mime_fields (&mime_info, &accept_info);
  689.     
  690.     new_var = push_array (neg->avail_vars);
  691.     memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
  692.         
  693.     clean_var_rec(&mime_info);
  694.     }
  695.  
  696.     closedir(dirp);
  697.     return OK;
  698. }
  699.  
  700.  
  701. /*****************************************************************
  702.  * And now for the code you've been waiting for... actually
  703.  * finding a match to the client's requirements.
  704.  */
  705.  
  706. /* Matching MIME types ... the star/star and foo/star commenting conventions
  707.  * are implemented here.  (You know what I mean by star/star, but just
  708.  * try mentioning those three characters in a C comment).  Using strcmp()
  709.  * is legit, because everything has already been smashed to lowercase.
  710.  *
  711.  * Note also that if we get an exact match on the media type, we update
  712.  * level_matched for use in level_cmp below...
  713.  */
  714.  
  715. int mime_match (accept_rec *accept, var_rec *avail)
  716. {
  717.     char *accept_type = accept->type_name;
  718.     char *avail_type = avail->type_name;
  719.     int len = strlen(accept_type);
  720.   
  721.     if (accept_type[0] == '*')    /* Anything matches star/star */
  722.     return 1; 
  723.     else if (accept_type[len - 1] == '*')
  724.     return !strncmp (accept_type, avail_type, len - 2);
  725.     else if (!strcmp (accept_type, avail_type)
  726.          || (!strcmp (accept_type, "text/html")
  727.          && (!strcmp(avail_type, INCLUDES_MAGIC_TYPE)
  728.              || !strcmp(avail_type, INCLUDES_MAGIC_TYPE3)))) {
  729.     if (accept->level >= avail->level) {
  730.         avail->level_matched = avail->level;
  731.         return 1;
  732.     }
  733.     }
  734.  
  735.     return OK;
  736. }
  737.  
  738. /* This code implements a piece of the tie-breaking algorithm between
  739.  * variants of equal quality.  This piece is the treatment of variants
  740.  * of the same base media type, but different levels.  What we want to
  741.  * return is the variant at the highest level that the client explicitly
  742.  * claimed to accept.
  743.  *
  744.  * If all the variants available are at a higher level than that, or if
  745.  * the client didn't say anything specific about this media type at all
  746.  * and these variants just got in on a wildcard, we prefer the lowest
  747.  * level, on grounds that that's the one that the client is least likely
  748.  * to choke on.
  749.  *
  750.  * (This is all motivated by treatment of levels in HTML --- we only
  751.  * want to give level 3 to browsers that explicitly ask for it; browsers
  752.  * that don't, including HTTP/0.9 browsers that only get the implicit
  753.  * "Accept: * / *" [space added to avoid confusing cpp --- no, that
  754.  * syntax doesn't really work] should get HTML2 if available).
  755.  *
  756.  * (Note that this code only comes into play when we are choosing among
  757.  * variants of equal quality, where the draft standard gives us a fair
  758.  * bit of leeway about what to do.  It ain't specified by the standard;
  759.  * rather, it is a choice made by this server about what to do in cases
  760.  * where the standard does not specify a unique course of action).
  761.  */
  762.  
  763. int level_cmp (var_rec *var1, var_rec *var2)
  764. {
  765.     /* Levels are only comparable between matching media types */
  766.  
  767.     if (var1->is_pseudo_html && !var2->is_pseudo_html)
  768.     return 0;
  769.     
  770.     if (!var1->is_pseudo_html && strcmp (var1->type_name, var2->type_name))
  771.     return 0;
  772.     
  773.     /* Take highest level that matched, if either did match. */
  774.     
  775.     if (var1->level_matched > var2->level_matched) return 1;
  776.     if (var1->level_matched < var2->level_matched) return -1;
  777.  
  778.     /* Neither matched.  Take lowest level, if there's a difference. */
  779.  
  780.     if (var1->level < var2->level) return 1;
  781.     if (var1->level > var2->level) return -1;
  782.  
  783.     /* Tied */
  784.  
  785.     return 0;
  786. }
  787.  
  788. /* Finding languages.  Note that we only match the substring specified
  789.  * by the Accept: line --- this is to allow "en" to match all subvariants
  790.  * of English.
  791.  *
  792.  * Again, strcmp() is legit because we've ditched case already.
  793.  */
  794.  
  795. int find_lang_index (array_header *accept_langs, char *lang)
  796. {
  797.     accept_rec *accs;
  798.     int i;
  799.  
  800.     if (!lang)
  801.     return -1;
  802.  
  803.     accs = (accept_rec *)accept_langs->elts;
  804.  
  805.     for (i = 0; i < accept_langs->nelts; ++i)
  806.     if (!strncmp (lang, accs[i].type_name, strlen(accs[i].type_name)))
  807.         return i;
  808.         
  809.     return -1;        
  810. }
  811.  
  812. /* This function returns the priority of a given language
  813.  * according to LanguagePriority.  It is used in case of a tie
  814.  * between several languages.
  815.  */
  816.  
  817. int find_default_index (neg_dir_config *conf, char *lang)
  818. {
  819.     array_header *arr;
  820.     int nelts;
  821.     char **elts;
  822.     int i;
  823.  
  824.     if (!lang)
  825.     return -1;
  826.  
  827.     arr = conf->language_priority;
  828.     nelts = arr->nelts;
  829.     elts = (char **) arr->elts;
  830.  
  831.     for (i = 0; i < nelts; ++i)
  832.         if (!strcasecmp (elts[i], lang))
  833.         return i;
  834.  
  835.     return -1;
  836. }
  837.  
  838. void find_lang_indexes (negotiation_state *neg)
  839. {
  840.     var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
  841.     int i;
  842.     int found_any = 0;
  843.     neg_dir_config *conf = NULL;
  844.     int naccept = neg->accept_langs->nelts;
  845.  
  846.     if (naccept == 0)
  847.     conf = (neg_dir_config *) get_module_config (neg->r->per_dir_config,
  848.                              &negotiation_module);
  849.  
  850.     for (i = 0; i < neg->avail_vars->nelts; ++i)
  851.     if (var_recs[i].quality > 0) {
  852.         int index;
  853.         if (naccept == 0)        /* Client doesn't care */
  854.         index = find_default_index (conf,
  855.                         var_recs[i].content_language);
  856.         else            /* Client has Accept-Language */
  857.         index = find_lang_index (neg->accept_langs,
  858.                      var_recs[i].content_language);
  859.  
  860.         var_recs[i].lang_index = index;
  861.         if (index >= 0) found_any = 1;
  862.     }
  863.  
  864.     /* If we have any variants in a language acceptable to the client,
  865.      * blow away everything that isn't.
  866.      */
  867.     
  868.     if (found_any)
  869.     for (i = 0; i < neg->avail_vars->nelts; ++i) 
  870.         if (var_recs[i].lang_index < 0)
  871.         var_recs[i].quality = 0;
  872. }
  873.  
  874. /* Finding content encodings.  Note that we assume that the client
  875.  * accepts the trivial encodings.  Strcmp() is legit because... aw, hell.
  876.  */
  877.  
  878. int is_identity_encoding (char *enc)
  879. {
  880.     return (!enc || !enc[0] || !strcmp (enc, "7bit") || !strcmp (enc, "8bit")
  881.         || !strcmp (enc, "binary"));
  882. }
  883.  
  884. int find_encoding (array_header *accept_encodings, char *enc)
  885. {
  886.     accept_rec *accs = (accept_rec *)accept_encodings->elts;
  887.     int i;
  888.  
  889.     if (is_identity_encoding(enc)) return 1.0;
  890.  
  891.     for (i = 0; i < accept_encodings->nelts; ++i)
  892.     if (!strcmp (enc, accs[i].type_name))
  893.         return 1;
  894.  
  895.     return 0;
  896. }
  897.  
  898. void do_encodings (negotiation_state *neg)
  899. {
  900.     var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
  901.     int i;
  902.  
  903.     /* If no Accept-Encoding is present, everything is acceptable */
  904.  
  905.     if (!neg->accept_encodings->nelts)
  906.     return;
  907.  
  908.     /* Lose any variant with an unacceptable content encoding */
  909.  
  910.     for (i = 0; i < neg->avail_vars->nelts; ++i)
  911.     if (var_recs[i].quality > 0
  912.         && !find_encoding (neg->accept_encodings,
  913.                    var_recs[i].content_encoding))
  914.         
  915.         var_recs[i].quality = 0;
  916. }
  917.  
  918. /* Determining the content length --- if the map didn't tell us,
  919.  * we have to do a stat() and remember for next time.
  920.  *
  921.  * Grump.  For shambhala, even the first stat here may well be
  922.  * redundant (for multiviews) with a stat() done by the sub_req
  923.  * machinery.  At some point, that ought to be fixed.
  924.  */
  925.  
  926. int find_content_length(negotiation_state *neg, var_rec *variant)
  927. {
  928.     struct stat statb;
  929.  
  930.     if (variant->bytes == 0) {
  931.         char *fullname = make_full_path (neg->pool, neg->dir_name,
  932.                      variant->file_name);
  933.     
  934.     if (stat (fullname, &statb) >= 0) variant->bytes = statb.st_size;
  935.     }
  936.  
  937.     return variant->bytes;
  938. }
  939.  
  940. /* The main event. */
  941.  
  942. var_rec *best_match(negotiation_state *neg)
  943. {
  944.     int i, j;
  945.     var_rec *best = NULL;
  946.     float best_quality = 0.0;
  947.     int levcmp;
  948.     
  949.     accept_rec *accept_recs = (accept_rec *)neg->accepts->elts;
  950.     var_rec *avail_recs = (var_rec *)neg->avail_vars->elts;
  951.  
  952.     /* Nuke variants which are unsuitable due to a content encoding,
  953.      * or possibly a language, which the client doesn't accept.
  954.      * (If we haven't *got* a variant in a language the client accepts,
  955.      * find_lang_indexes keeps 'em all, so we still wind up serving
  956.      * something...).
  957.      */
  958.     
  959.     do_encodings (neg);
  960.     find_lang_indexes (neg);
  961.     
  962.     for (i = 0; i < neg->accepts->nelts; ++i) {
  963.  
  964.     accept_rec *type = &accept_recs[i];
  965.     
  966.     for (j = 0; j < neg->avail_vars->nelts; ++j) {
  967.         
  968.         var_rec *variant = &avail_recs[j];
  969.         float q = type->quality * variant->quality;
  970.         
  971.         /* If we've already rejected this variant, don't waste time */
  972.         
  973.         if (q == 0.0) continue;    
  974.         
  975.         /* If media types don't match, forget it.
  976.          * (This includes the level check).
  977.          */
  978.         
  979.         if (!mime_match(type, variant)) continue;
  980.  
  981.         /* Check maxbytes */
  982.         
  983.         if (type->max_bytes > 0
  984.         && (find_content_length(neg, variant)
  985.             > type->max_bytes))
  986.         continue;
  987.         
  988.         /* If it lasted this far, consider it ---
  989.          * If better quality than our current best, take it.
  990.          * If equal quality, *maybe* take it.
  991.          *
  992.          * Note that the current http draft specifies no particular
  993.          * behavior for variants which tie in quality; the server
  994.          * can, at its option, return a 300 response listing all
  995.          * of them (and perhaps the others), or choose one of the
  996.          * tied variants by whatever means it likes.  This server
  997.          * breaks ties as follows, in order:
  998.          *
  999.          * By order of languages in Accept-language, to give the
  1000.          * client a way to specify a language preference.  I'd prefer
  1001.          * to give this precedence over media type, but the standard
  1002.          * doesn't allow for that.
  1003.          *
  1004.          * By level preference, as defined by level_cmp above.
  1005.          *
  1006.          * By order of Accept: header matched, so that the order in
  1007.          * which media types are named by the client functions as a
  1008.          * preference order, if the client didn't give us explicit
  1009.          * quality values.
  1010.          *
  1011.          * Finally, by content_length, so that among variants which
  1012.          * have the same quality, language and content_type (including
  1013.          * level) we ship the one that saps the least bandwidth.
  1014.          */
  1015.         
  1016.         if (q > best_quality
  1017.         || (q == best_quality
  1018.             && (variant->lang_index < best->lang_index
  1019.             || (variant->lang_index == best->lang_index
  1020.                 && ((levcmp = level_cmp (variant, best)) == 1
  1021.                 || (levcmp == 0
  1022.                     && !strcmp (variant->type_name,
  1023.                         best->type_name)
  1024.                     && (find_content_length(neg, variant)
  1025.                     <
  1026.                     find_content_length(neg, best))))))))
  1027.         {
  1028.         best = variant;
  1029.         best_quality = q;
  1030.         }
  1031.     }
  1032.     }
  1033.  
  1034.     return best;
  1035. }
  1036.  
  1037. /****************************************************************
  1038.  *
  1039.  * Executive...
  1040.  */
  1041.  
  1042. int handle_map_file (request_rec *r)
  1043. {
  1044.     negotiation_state *neg = parse_accept_headers (r);
  1045.     var_rec *best;
  1046.     int res;
  1047.     
  1048.     char *udir;
  1049.     
  1050.     if ((res = read_type_map (neg, r->filename))) return res;
  1051.     
  1052.     maybe_add_default_encodings(neg, 0);
  1053.     
  1054.     if (!(best = best_match(neg))) {
  1055.       /* Should be a 406 */
  1056.       log_reason ("no acceptable variant", r->filename, r);
  1057.       return NOT_FOUND;
  1058.     }
  1059.  
  1060.     if (!do_cache_negotiated_docs(r->server)) r->no_cache = 1;
  1061.     udir = make_dirstr (r->pool, r->uri, count_dirs (r->uri));
  1062.     udir = escape_uri(r->pool, udir);
  1063.     internal_redirect (make_full_path (r->pool, udir, best->file_name), r);
  1064.     return OK;
  1065. }
  1066.  
  1067. int handle_multi (request_rec *r)
  1068. {
  1069.     negotiation_state *neg;
  1070.     var_rec *best;
  1071.     request_rec *sub_req;
  1072.     int res;
  1073.     
  1074.     if (r->finfo.st_mode != 0 || !(allow_options (r) & OPT_MULTI))
  1075.         return DECLINED;
  1076.     
  1077.     neg = parse_accept_headers (r);
  1078.     
  1079.     if ((res = read_types_multi (neg))) return res;
  1080.     
  1081.     maybe_add_default_encodings(neg,
  1082.                 r->method_number != M_GET
  1083.                   || r->args || r->path_info);
  1084.     
  1085.     if (neg->avail_vars->nelts == 0) return DECLINED;
  1086.     
  1087.     if (!(best = best_match(neg))) {
  1088.       /* Should be a 406 */
  1089.       log_reason ("no acceptable variant", r->filename, r);
  1090.       return NOT_FOUND;
  1091.     }
  1092.  
  1093.     if (! (sub_req = best->sub_req)) {
  1094.         /* We got this out of a map file, so we don't actually have
  1095.      * a sub_req structure yet.  Get one now.
  1096.      */
  1097.       
  1098.         sub_req = sub_req_lookup_file (best->file_name, r);
  1099.     if (sub_req->status != 200) return sub_req->status;
  1100.     }
  1101.       
  1102.     /* BLETCH --- don't multi-resolve non-ordinary files */
  1103.  
  1104.     if (!S_ISREG(sub_req->finfo.st_mode)) return NOT_FOUND;
  1105.     
  1106.     /* Otherwise, use it. */
  1107.     
  1108.     if (!do_cache_negotiated_docs(r->server)) r->no_cache = 1;
  1109.     r->filename = sub_req->filename;
  1110.     r->content_type = sub_req->content_type;
  1111.     r->content_encoding = sub_req->content_encoding;
  1112.     r->content_language = sub_req->content_language;
  1113.     r->finfo = sub_req->finfo;
  1114.     
  1115.     return OK;
  1116. }
  1117.  
  1118. handler_rec negotiation_handlers[] = {
  1119. { MAP_FILE_MAGIC_TYPE, handle_map_file },
  1120. { NULL }
  1121. };
  1122.  
  1123. module negotiation_module = {
  1124.    STANDARD_MODULE_STUFF,
  1125.    NULL,            /* initializer */
  1126.    create_neg_dir_config,    /* dir config creater */
  1127.    merge_neg_dir_configs,    /* dir merger --- default is to override */
  1128.    NULL,            /* server config */
  1129.    NULL,            /* merge server config */
  1130.    negotiation_cmds,        /* command table */
  1131.    negotiation_handlers,    /* handlers */
  1132.    NULL,            /* filename translation */
  1133.    NULL,            /* check_user_id */
  1134.    NULL,            /* check auth */
  1135.    NULL,            /* check access */
  1136.    handle_multi,        /* type_checker */
  1137.    NULL,            /* fixups */
  1138.    NULL                /* logger */
  1139. };
  1140.